package my.suveng.util.dynamic.datatsource;

import cn.hutool.core.lang.caller.CallerUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import my.suveng.util.dynamic.datatsource.properties.Datasource;
import my.suveng.util.dynamic.datatsource.properties.DynamicDataSourceProperties;
import my.suveng.util.dynamic.datatsource.route.DataSourcePolling;
import my.suveng.util.dynamic.datatsource.route.DynamicDataSource;
import my.suveng.util.log.PlusLogFactoryHutool;
import org.apache.ibatis.session.SqlSessionFactory;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据源配置
 * @author suwenguang
 **/
@Data
@Configuration
public class DynamicDataSourceConfig {
    //hutool日志
    private static final Log log = LogFactory.setCurrentLogFactory(new PlusLogFactoryHutool()).getLog(CallerUtil.getCaller().getName());


    private DynamicDataSourceProperties dynamicDataSourceProperties;

    public DynamicDataSourceConfig(@Autowired DynamicDataSourceProperties dynamicDataSourceProperties) {
        this.dynamicDataSourceProperties = dynamicDataSourceProperties;
    }

    private List<DataSource> dataSourceList = new ArrayList<>();

    @Bean
    public DataSource master() throws ClassNotFoundException {
        Datasource master = dynamicDataSourceProperties.getMaster();
        if (ObjectUtil.isEmpty(master)) {
            throw new IllegalArgumentException("master没有配置");
        }
        DataSource result = null;
        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();


        String type = dynamicDataSourceProperties.getType();
        Class<DataSource> typeClass = (Class<DataSource>) Class.forName(type);
        dataSourceBuilder.type(typeClass);

        result = druidAdapter(master, result, typeClass);

        if (ObjectUtil.isEmpty(result)) {
            dataSourceBuilder.driverClassName(master.getDriverClass());
            dataSourceBuilder.url(master.getUrl());
            dataSourceBuilder.username(master.getUsername());
            dataSourceBuilder.password(master.getPassword());
            result = dataSourceBuilder.build();
        }

        dataSourceList.add(0, result);
        return result;
    }


    @Bean
    @Primary
    public DynamicDataSource dataSource() throws ClassNotFoundException {
        Map<Object, Object> targetDataSources = new HashMap<>();
        //加入master
        DataSource master = master();
        targetDataSources.put("master", master);
        //动态构建slave
        getSlaves(targetDataSources);
        DataSourcePolling.DATASOURCES.add(0, "master");
        return new DynamicDataSource(master, targetDataSources);
    }


    // mybatis事务管理器
    @Bean(name = "dataSourceTransactionManager")
    @ConditionalOnClass({DataSourceTransactionManager.class})
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }


    @Bean(name = "sqlSessionFactory")
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        String mapperLocation = dynamicDataSourceProperties.getMapperLocation();
        if (StrUtil.isBlank(mapperLocation)) {
            //默认值
            mapperLocation = "my.suveng";
        }
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:" + mapperLocation));
        if (StrUtil.isNotBlank(dynamicDataSourceProperties.getMapperConfigLoaction())) {
            bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:" + dynamicDataSourceProperties.getMapperConfigLoaction()));
        }
        return bean.getObject();
    }


    //jpa事务管理器
    @Primary
    @Bean(value = "transactionManager")
    public PlatformTransactionManager transactionManager(DataSource dataSource) throws ClassNotFoundException {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setDataSource(dataSource);
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return jpaTransactionManager;
    }

    @Bean
    @ConditionalOnClass(value = {JpaVendorAdapter.class, HibernateJpaVendorAdapter.class})
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(true);
        adapter.setDatabase(Database.MYSQL);
        adapter.setGenerateDdl(true);
        return adapter;
    }


    @Bean(name = "entityManagerFactory")
    @ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class})
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws ClassNotFoundException {
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setDataSource(dataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter());
        String jpaPackagesToScan = dynamicDataSourceProperties.getJpaPackagesToScan();
        if (StrUtil.isBlank(jpaPackagesToScan)) {
            //默认值
            jpaPackagesToScan = "my.suveng";
        }
        entityManager.setPackagesToScan(jpaPackagesToScan);// entity package
        entityManager.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        return entityManager;
    }

    private DataSource druidAdapter(Datasource master, DataSource result, Class<DataSource> typeClass) {
        if (typeClass.equals(DruidDataSource.class)) {
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setUrl(master.getUrl());
            druidDataSource.setUsername(master.getUsername());
            druidDataSource.setPassword(master.getPassword());
            druidDataSource.setDriverClassName(master.getDriverClass());
            druidDataSource.setTestWhileIdle(true);
            druidDataSource.setValidationQuery("select 1");
            result = druidDataSource;
        }
        return result;
    }

    private void getSlaves(Map<Object, Object> targetDataSources) {
        Datasource[] slaves = dynamicDataSourceProperties.getSlaves();
        if (ArrayUtil.isEmpty(slaves)) {
            return;
        }
        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();

        for (int i = 0; i < slaves.length; i++) {
            String type = dynamicDataSourceProperties.getType();
            Class<DataSource> typeClass;
            DataSource result = null;
            try {
                typeClass = (Class<DataSource>) Class.forName(type);
                result = druidAdapter(slaves[i], result, typeClass);
                if (ObjectUtil.isEmpty(result)) {
                    dataSourceBuilder.type(typeClass);
                    dataSourceBuilder.url(slaves[i].getUrl());
                    dataSourceBuilder.username(slaves[i].getUsername());
                    dataSourceBuilder.password(slaves[i].getPassword());
                    result = dataSourceBuilder.build();
                }
            } catch (ClassNotFoundException ex) {
                log.error("构建数据源配置失败,检查配置的type");
                return;
            }

            String key = "slave" + i;
            targetDataSources.put(key, result);
            DataSourcePolling.DATASOURCES.add(key);
            dataSourceList.add(result);
        }
    }
}
